#include "endpoint.h"
using namespace boost;
int  client_end_point() {
	// Step 1. Assume that the client application has already 
// obtained the IP-address and the protocol port number.
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;

	// Used to store information about error that happens
	// while parsing the raw IP-address.
	boost::system::error_code ec;
	// Step 2. Using IP protocol version independent address
	// representation.
	asio::ip::address ip_address =
		asio::ip::address::from_string(raw_ip_address, ec);

	if (ec.value() != 0) {
		// Provided IP address is invalid. Breaking execution.
		std::cout
			<< "Failed to parse the IP address. Error code = "
			<< ec.value() << ". Message: " << ec.message();
		return ec.value();
	}

	// Step 3.
	asio::ip::tcp::endpoint ep(ip_address, port_num);

	// Step 4. The endpoint is ready and can be used to specify a 
	// particular server in the network the client wants to 
	// communicate with.

	return 0;
}

int  server_end_point(){
	// Step 1. Here we assume that the server application has
//already obtained the protocol port number.
	unsigned short port_num = 3333;

	// Step 2. Create special object of asio::ip::address class
	// that specifies all IP-addresses available on the host. Note
	// that here we assume that server works over IPv6 protocol.
	asio::ip::address ip_address = asio::ip::address_v6::any();

	// Step 3.
	asio::ip::tcp::endpoint ep(ip_address, port_num);

	// Step 4. The endpoint is created and can be used to 
	// specify the IP addresses and a port number on which 
	// the server application wants to listen for incoming 
	// connections.

	return 0;
}

int create_tcp_socket() {
	// Step 1. An instance of 'io_service' class is required by
			// socket constructor. 
	asio::io_context  ios;

	// Step 2. Creating an object of 'tcp' class representing
	// a TCP protocol with IPv4 as underlying protocol.
	asio::ip::tcp protocol = asio::ip::tcp::v4();

	// Step 3. Instantiating an active TCP socket object.
	asio::ip::tcp::socket sock(ios);

	// Used to store information about error that happens
	// while opening the socket.
	boost::system::error_code ec;

	// Step 4. Opening the socket.
	sock.open(protocol, ec);

	if (ec.value() != 0) {
		// Failed to open the socket.
		std::cout
			<< "Failed to open the socket! Error code = "
			<< ec.value() << ". Message: " << ec.message();
		return ec.value();
	}

	return 0;
}

int  create_acceptor_socket() {
	// Step 1. An instance of 'io_service' class is required by
		// socket constructor. 
	asio::io_context ios;

	// Step 2. Creating an object of 'tcp' class representing
			// a TCP protocol with IPv6 as underlying protocol.
	asio::ip::tcp protocol = asio::ip::tcp::v6();

	// Step 3. Instantiating an acceptor socket object.
	asio::ip::tcp::acceptor acceptor(ios);

	// Used to store information about error that happens
	// while opening the acceptor socket.
	boost::system::error_code ec;

	// Step 4. Opening the acceptor socket.
	acceptor.open(protocol, ec);

	if (ec.value() != 0) {
		// Failed to open the socket.
		std::cout
			<< "Failed to open the acceptor socket!"
			<< "Error code = "
			<< ec.value() << ". Message: " << ec.message();
		return ec.value();
	}

	return 0;
}


int  bind_acceptor_socket() {

	// Step 1. Here we assume that the server application has
		// already obtained the protocol port number.
	unsigned short port_num = 3333;

	// Step 2. Creating an endpoint.
	asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(),
		port_num);

	// Used by 'acceptor' class constructor.
	asio::io_context  ios;

	// Step 3. Creating and opening an acceptor socket.
	asio::ip::tcp::acceptor acceptor(ios, ep.protocol());

	boost::system::error_code ec;

	// Step 4. Binding the acceptor socket.
	acceptor.bind(ep, ec);

	// Handling errors if any.
	if (ec.value() != 0) {
		// Failed to bind the acceptor socket. Breaking
		// execution.
		std::cout << "Failed to bind the acceptor socket."
			<< "Error code = " << ec.value() << ". Message: "
			<< ec.message();

		return ec.value();
	}

	return 0;
}

int  connect_to_end() {
	// Step 1. Assume that the client application has already
			// obtained the IP address and protocol port number of the
			// target server.
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;

	try {
		// Step 2. Creating an endpoint designating 
		// a target server application.
		asio::ip::tcp::endpoint
			ep(asio::ip::address::from_string(raw_ip_address),
				port_num);

		asio::io_context ios;

		// Step 3. Creating and opening a socket.
		asio::ip::tcp::socket sock(ios, ep.protocol());

		// Step 4. Connecting a socket.
		sock.connect(ep);

		// At this point socket 'sock' is connected to 
		// the server application and can be used
		// to send data to or receive data from it.
	}
	// Overloads of asio::ip::address::from_string() and 
	// asio::ip::tcp::socket::connect() used here throw
	// exceptions in case of error condition.
	catch (system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

int dns_connect_to_end() {
	// Step1. Assume that the client application has already
		// obtained the DNS name and protocol port number and
		// represented them as strings.
	std::string host = "samplehost.book";
	std::string port_num = "3333";

	// Used by a 'resolver' and a 'socket'.
	asio::io_context  ios;

	// Creating a resolver's query.
	asio::ip::tcp::resolver::query resolver_query(host, port_num,
		asio::ip::tcp::resolver::query::numeric_service);

	// Creating a resolver.
	asio::ip::tcp::resolver resolver(ios);

	try {
		// Step 2. Resolving a DNS name.
		asio::ip::tcp::resolver::iterator it =
			resolver.resolve(resolver_query);

		// Step 3. Creating a socket.
		asio::ip::tcp::socket sock(ios);

		// Step 4. asio::connect() method iterates over
		// each endpoint until successfully connects to one
		// of them. It will throw an exception if it fails
		// to connect to all the endpoints or if other
		// error occurs.
		asio::connect(sock, it);

		// At this point socket 'sock' is connected to 
		// the server application and can be used
		// to send data to or receive data from it.
	}
	// Overloads of asio::ip::tcp::resolver::resolve and 
	// asio::connect() used here throw
	// exceptions in case of error condition.
	catch (system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}

	return 0;
}
